package cn.eonshine.cuba.inf.modular.common.service;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Maps;

import cn.eonshine.cuba.inf.common.http.InfStatus;
import cn.eonshine.cuba.inf.common.http.Message;
import cn.eonshine.cuba.inf.modular.api.ICommonMapper;
import cn.eonshine.cuba.inf.modular.api.ICommonService;
import cn.eonshine.cuba.inf.modular.basic.service.IMappingService;
import cn.eonshine.cuba.inf.modular.security.server.IKeyService;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;

@Service
@Transactional
public abstract class AbstractCommonService<M extends ICommonMapper<T>, T> extends ServiceImpl<M, T> implements ICommonService<T> {

    protected Logger log = LoggerFactory.getLogger(this.getClass());
    
	@Autowired
	protected IKeyService keyService;
	@Autowired
	protected IMappingService mappingService;
	
	protected int isUpdate;
	
	public Message queryDataList(Message clientMessage) {
		QueryWrapper<T> entity = customerQueryWrapper();
		IPage<T> page = null;
		if(isUseCutomerSelectSQL()) {
			page = selectInfPage(new Page<T>(1, 500), entity);
		}else {
			page = page(new Page<T>(1, 500), entity);
		}
		log.debug("Query result total : " + page.getRecords().size());
		return keyService.serverEncryptData(JSONUtil.toJsonStr(page.getRecords()), page.getRecords().size(), clientMessage.getPubKey());
	}
	

	public Message changeLsmsUpdate(Message clientMessage, int isUpdate) {
		this.isUpdate = isUpdate;
		Message message = new Message();
		int status = InfStatus.INF_OK;
		String msg = null;
		int count = 0;
		String decryptServerPubKey = null; // 明文服务端的PubKey
		try {
			String clientMsg = keyService.getServerDecryptInfo(clientMessage.getMsg());
			JSONObject map = JSONUtil.parseObj(clientMsg);
			// Success data handle
			JSONArray successListData = (JSONArray) map.get(InfStatus.TRANS_STATUS.SUCCESS);
			if(successListData.size()>0) {
				count = messageUpdateHandle(successListData.toString(), count);
			}
			// Fail data handle
			JSONArray failListData = (JSONArray) map.get(InfStatus.TRANS_STATUS.FAIL);
			if(failListData.size()>0) {
				count = messageUpdateFailHandle(failListData.toString(), count);
			}
			// 将服务端的PubKey解密成明文
			decryptServerPubKey = keyService.getServerDecryptInfo(clientMessage.getPubKey());
			// 将客户端的PubKey加密成密文
			String localClientPubKey = keyService.getLocalPubKeyBase64();
			String cryptePubKey = keyService.getServerEncryptInfo(localClientPubKey, decryptServerPubKey);

			message.setPubKey(cryptePubKey);

		} catch (UnrecoverableKeyException e) {
			status = InfStatus.INF_ERROR;
			msg = Message.getErrorMsg(e);
			e.printStackTrace();
		} catch (KeyStoreException e) {
			status = InfStatus.INF_ERROR;
			msg = Message.getErrorMsg(e);
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			status = InfStatus.INF_ERROR;
			msg = Message.getErrorMsg(e);
			e.printStackTrace();
		} catch (CertificateException e) {
			status = InfStatus.INF_ERROR;
			msg = Message.getErrorMsg(e);
			e.printStackTrace();
		} catch (IOException e) {
			status = InfStatus.INF_ERROR;
			msg = Message.getErrorMsg(e);
			e.printStackTrace();
		} catch (Exception e) {
			// 数据更新操作异常
			status = InfStatus.INF_ERROR;
			msg = Message.getErrorMsg(e);
			e.printStackTrace();
		}

		message.setStatus(status);
		if (StrUtil.isNotBlank(msg)) {
			msg = keyService.getServerEncryptInfo(msg, decryptServerPubKey);
		} else {
			message.setPubKey(null);
		}
		message.setMsg(msg);
		message.setCount(count);

		return message;
	}
	
	/**
	 * 
	 * @Title: changeMessage   
	 * @Description: handle message for update 
	 * by Alan 2019-03-25 22:17:16
	 * @param clientMsg decrypt Info message
	 * @param count init 0 number
	 * @throws Exception      
	 */
	public int messageUpdateHandle(String clientMsg, int count) throws Exception{
		List<T> lists = (List<T>) JSONUtil.toList(JSONUtil.parseArray(clientMsg), getClassT());
		for (T t : lists) {
			count = transportDataHandle(t, count);
			mappingHandle(t);
		}
		return count;
	}
	/**
	 * 
	 * @Title: messageUpdateFailHandle   
	 * @Description: handle error message of update 
	 * by Alan 2019-03-25 23:55:03
	 * @param clientMsg decrypt Info message
	 * @param count success total number
	 * @throws Exception      
	 */
	public int messageUpdateFailHandle(String clientMsg, int count) throws Exception{
		List<T> lists = (List<T>) JSONUtil.toList(JSONUtil.parseArray(clientMsg), getClassT());
		for (T t : lists) {
			Map<String, Object> map = BeanUtil.beanToMap(t);
			map = convertBeanFieldToDBField(map, getClassT());
			QueryWrapper<T> entity = new QueryWrapper<T>();
			entity.allEq(map);
			
			T obj = getClassT().newInstance();
			Method method = ReflectUtil.getMethodIgnoreCase(getClassT(), LSMS_UPDATE_FUNC, null);
			if(method != null) {
				Object val = convertLsmsUpdateVal(method, InfStatus.LSMS_UPDATE_EXTRACT_YES);
				ReflectUtil.invoke(obj, method, val);
				boolean isSuccess = update(obj, entity);
				if(isSuccess) {
					count++;
				}
			}
		}
		return count;
	}
	
	private static final String LSMS_UPDATE_FUNC="setLsmsUpdate";
	/**
	 * 
	 * @Title: convertLsmsUpdateVal   
	 * @Description: TODO 
	 * by Alan 2019-03-26 09:27:12
	 * @param method 
	 * @param val
	 */
	private  Object convertLsmsUpdateVal(Method method, int val) {
		Type[] paramTypeList = method.getGenericParameterTypes();
		for (Type paramType : paramTypeList) { 
			log.debug(paramType.getClass().toString());
			if(BigDecimal.class.getName().equals(paramType.getTypeName())) {
				return BigDecimal.valueOf(val);
			} else if(String.class.getName().equals(paramType.getTypeName())) {
				return val+"";
			} else if(int.class.getName().equals(paramType.getTypeName())) {
				return val;
			} else if(Integer.class.getName().equals(paramType.getTypeName())) {
				return Integer.valueOf(val);
			}
		}
		return null;
	}
	
	/**
	 * 
	 * @Title: convertBeanFeildToDBField   
	 * @Description: convert Bean key to db field key 
	 * by Alan 2019-03-26 09:44:14
	 * @param map
	 * @param clazz
	 */
	private Map<String, Object> convertBeanFieldToDBField(Map<String, Object> map, Class<?> clazz){
		Map<String, Object> convertMap = Maps.newHashMap();
		for (Entry<String, Object> ele : map.entrySet()) {
			if("dataInputDatetime".equals(ele.getKey()) 
					|| ele.getValue() == null) {
				continue;
			}
			String dbField =  ele.getKey();
			Field field = ReflectUtil.getField(clazz, dbField);
			TableField tf = field.getAnnotation(TableField.class);
			if(tf == null) {
				TableId tid = field.getAnnotation(TableId.class);
				if(tid != null) {
					dbField = tid.value();
				}
			}else {
				dbField=tf.value();
			}
			if(StringUtils.isNoneBlank(dbField)) {
				convertMap.put(dbField, ele.getValue());
			}
		}
		return convertMap;
	}
	
	/**
	 * 
	 * @Title: getClassT   
	 * @Description: TODO 
	 * @author: Alan
	 * @date:   2019-02-14 10:45:41
	 */
	public Class<T> getClassT() throws InstantiationException, IllegalAccessException {
		Type sType = getClass().getGenericSuperclass();
		Type[] generics = ((ParameterizedType) sType).getActualTypeArguments();
		@SuppressWarnings("unchecked")
		Class<T> mTClass = (Class<T>) (generics[1]);
		return mTClass;
	}
	
	/**
	 * 
	 * <p>Title: selectInf</p>   
	 * <p>Description: </p>   
	 * @param page 
	 * @param entitys
	 * @author: Alan
	 * @date:   2019-02-18 21:55:56
	 */
	public Page<T> selectInfPage(Page<T> page, Wrapper<T> entitys) {
		return page.setRecords(this.baseMapper.selectInfList(page, entitys));
	}
	
	/**
	 * 
	 * @Title: customerQueryWrapper   
	 * @Description: for query list where conditions, default  lsms_update = 1 , order by  code asc
	 * @author: Alan
	 * @date:   2019-03-13 11:27:45
	 */
	public QueryWrapper<T> customerQueryWrapper(){
		QueryWrapper<T> entity = new QueryWrapper<T>();
		entity.eq("lsms_update", InfStatus.LSMS_UPDATE_EXTRACT_NO);
		entity.orderByAsc("lsms_update");
		return entity;
	}
	
	/**
	 * 
	 * @Title: transPortDataHandle   
	 * @Description: transport each data handle 
	 * @author: Alan
	 * @date:   2019-02-14 10:34:06
	 * @param obj    Object
	 * @param count handle success data number
	 * @throws Exception      
	 *     int   return the count number
	 */
	public abstract int transportDataHandle(T obj, int count) throws Exception;
	
	/**
	 * 
	 * @Title: mappingHandle   
	 * @Description: handle transport data to mapping table 
	 * @author: Alan
	 * @date:   2019-02-14 10:47:31
	 * @param obj
	 * @throws Exception      
	 *     void      
	 */
	public abstract void mappingHandle(T obj) throws Exception;
	
	/**
	 * 
	 * @Title: deleteHandle   
	 * @Description: handle return data to delete 
	 * @author: Alan
	 * @date:   2019-02-14 10:56:28
	 * @param clientMsg
	 * @throws Exception      
	 *     void      
	 */
	public void deleteHandle(String clientMsg) throws Exception{
		Map<String, Object> columnMap = Maps.newHashMap();
		columnMap.put("lsms_update", InfStatus.LSMS_UPDATE_EXTRACT_YES);
		removeByMap(columnMap);
	}

	/**
	 * 
	 * <p>Title: deleteData</p>   
	 * <p>Description: delete data </p>   
	 * @param clientMessage
	 * @see cn.eonshine.cuba.inf.modular.api.ICommonService#deleteData(cn.eonshine.cuba.inf.common.http.Message)   
	 * @author: Alan
	 * @date:   2019-02-14 10:36:05
	 */
	public Message deleteData(Message clientMessage) {
		Message message = new Message();
		int status = InfStatus.INF_OK;
		String msg = null;
		int count = 0;
		try {
			String clientMsg = keyService.getServerDecryptInfo(clientMessage.getMsg());
			log.info(clientMsg);
//			deleteHandle(clientMsg);
		} catch (UnrecoverableKeyException e) {
			status = InfStatus.INF_ERROR;
			msg = Message.getErrorMsg(e);
			e.printStackTrace();
		} catch (KeyStoreException e) {
			status = InfStatus.INF_ERROR;
			msg = Message.getErrorMsg(e);
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			status = InfStatus.INF_ERROR;
			msg = Message.getErrorMsg(e);
			e.printStackTrace();
		} catch (CertificateException e) {
			status = InfStatus.INF_ERROR;
			msg = Message.getErrorMsg(e);
			e.printStackTrace();
		} catch (IOException e) {
			status = InfStatus.INF_ERROR;
			msg = Message.getErrorMsg(e);
			e.printStackTrace();
		} catch (Exception e) {
			status = InfStatus.INF_ERROR;
			msg = Message.getErrorMsg(e);
			e.printStackTrace();
		}

		message.setStatus(status);
		message.setMsg(msg);
		message.setCount(count);

		return message;
	}
	
	/**
	 * 
	 * @Title: isUseCutomerSelectSQL   
	 * @Description: is user selectInfPage query list sql : <br>
	 * 	true - use customer selectInfPage function<br>
	 *  function see: cn.eonshine.cuba.inf.modular.common.service.AbstractCommonService#selectInfPage<br>
	 *  false - use framework selectPage function<br>
	 * @author: Alan
	 * @date:   2019-02-19 10:46:35
	 */
	public abstract boolean isUseCutomerSelectSQL();
}
